package cn.angis.common.config;

import cn.angis.common.api.ApiErrorCode;
import cn.angis.common.model.R;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.dao.SaTokenDaoForRedisson;
import cn.dev33.satoken.exception.DisableServiceException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.solon.integration.SaTokenInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.druid.DbType;
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import com.beetl.sql.pref.PerformanceConfig;
import com.power4j.kit.seq.core.Sequence;
import com.power4j.kit.seq.persistent.Partitions;
import com.power4j.kit.seq.persistent.SeqHolder;
import com.power4j.kit.seq.persistent.provider.MySqlSynchronizer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import org.beetl.sql.core.ConnectionSource;
import org.beetl.sql.core.ConnectionSourceHelper;
import org.beetl.sql.core.SQLManager;
import org.beetl.sql.core.SQLManagerBuilder;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Condition;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Inject;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisClient;
import org.redisson.client.RedisClientConfig;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.config.Config;
import org.redisson.solon.RedissonClientOriginalSupplier;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Properties;


@Configuration
public class ConfigBean {

    @Bean
    public RedissonClient redissonClient(@Inject("${redis}") RedissonClientOriginalSupplier redissonSupplier) {
//        Codec codec = new JsonJacksonCodec();
//        RedissonClient redisson = redissonSupplier.get();
//        redisson.getConfig().setCodec(codec);
        return redissonSupplier.get();
    }


    @Bean
    @Condition(onClass = RedissonClient.class)
    public RedisClient getRedisConnection(RedissonClient redissonClient) throws IOException {
        Config configX = redissonClient.getConfig();
        EventLoopGroup group = new NioEventLoopGroup();
        RedisClientConfig config = new RedisClientConfig();
        //读取配置文件 暂时写死
        config.setAddress(configX.useSingleServer().getAddress());
        config.setDatabase(configX.useSingleServer().getDatabase())
                .setPassword(configX.useSingleServer().getPassword())
                .setClientName(configX.useSingleServer().getClientName())
                .setGroup(group);
//        System.out.println(redisClient.connect().sync(StringCodec.INSTANCE, RedisCommands.INFO_ALL).toString());
        Map<String, Object> info = RedisClient.create(config).connect().sync(StringCodec.INSTANCE, RedisCommands.INFO_ALL);
        return RedisClient.create(config);
    }


    @Bean
    @Condition(onClass = DataSource.class)
    public Sequence<Long> sequence(DataSource dataSource) {
        MySqlSynchronizer mySqlSynchronizer = new MySqlSynchronizer("seq_registry", dataSource);
        return SeqHolder.builder()
                .name("star")
                .synchronizer(mySqlSynchronizer)
                .partitionFunc(Partitions.MONTHLY)
                .initValue(0000000000L)
                .poolSize(10)
                .build();

        // @formatter:on
    }

    @Bean
    @Condition(onClass = RedissonClient.class)
    public SaTokenDao saTokenDaoInit(RedissonClient redissonClient) {
        return new SaTokenDaoForRedisson(redissonClient);
    }

    @Bean(initMethod="init", destroyMethod="close")
    public DataSource druid(@Inject("${database}") Properties properties) throws SQLException {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.configFromProperties(properties);
        List<Filter> filters = dataSource.getProxyFilters();
        for (Filter filter : filters) {
            if (filter instanceof WallFilter) {
                WallConfig wallConfig = new WallConfig();
                wallConfig.setSelectWhereAlwayTrueCheck(false);
                ((WallFilter) filter).setConfig(wallConfig);
            }
            if (filter instanceof Slf4jLogFilter) {
                ((Slf4jLogFilter) filter).setStatementExecutableSqlLogEnable(true);
                ((Slf4jLogFilter) filter).setStatementLogEnabled(false);
                ((Slf4jLogFilter) filter).setConnectionLogEnabled(false);
                ((Slf4jLogFilter) filter).setResultSetLogEnabled(false);
//                System.out.println(((Slf4jLogFilter) filter).isStatementLogEnabled());
//                System.out.println(((Slf4jLogFilter) filter).isResultSetLogEnabled());
            }
            if (filter instanceof StatFilter) {
                ((StatFilter) filter).setDbType(DbType.mysql);
                ((StatFilter) filter).setLogSlowSql(true);
                ((StatFilter) filter).setMergeSql(true);
                ((StatFilter) filter).setSlowSqlMillis(500);
            }
        }
        return dataSource;
    }
    @Bean
    @Condition(onClass = DataSource.class)
    public void init(DataSource ds) {
        ConnectionSource source = ConnectionSourceHelper.getSingle(ds);
        SQLManagerBuilder builder = new SQLManagerBuilder(source);
        SQLManager sqlManager = builder.build();
        PerformanceConfig performanceConfig = new PerformanceConfig();
        performanceConfig.config(sqlManager);
        SQLManager.javabeanStrict(false);
    }

    /**
     * 注册 [sa-token全局过滤器]
     *
     * @ignore
     */
    @Bean
    public SaTokenInterceptor tokenPathFilter() {
        return new SaTokenInterceptor()

                // 指定 [拦截路由] 与 [放行路由]
                .addInclude("/**").addExclude("/favicon.ico", "/login", "/api/login")

                // 认证函数: 每次请求执行
                .setAuth(r -> {
                    SaRouter.match("/**", StpUtil::checkLogin);
                })

                // 异常处理函数：每次认证函数发生异常时执行此函数
                .setError(e -> {
                    String message = "";
                    if (e instanceof NotLoginException) {
                        switch (((NotLoginException) e).getType()) {
                            case NotLoginException.NOT_TOKEN:
                                message = NotLoginException.NOT_TOKEN_MESSAGE;
                                break;
                            case NotLoginException.INVALID_TOKEN:
                                message = NotLoginException.INVALID_TOKEN_MESSAGE;
                                break;
                            case NotLoginException.TOKEN_TIMEOUT:
                                message = NotLoginException.TOKEN_TIMEOUT_MESSAGE;
                                break;
                            case NotLoginException.BE_REPLACED:
                                message = NotLoginException.BE_REPLACED_MESSAGE;
                                break;
                            case NotLoginException.KICK_OUT:
                                message = NotLoginException.KICK_OUT_MESSAGE;
                                break;
                            default:
                                message = NotLoginException.DEFAULT_MESSAGE;
                                break;
                        }
                        return R.restResult(ApiErrorCode.TOKEN_ERROR, message);
                    } else if (e instanceof NotRoleException) {
                        return R.restResult(ApiErrorCode.USER_NOROLE_LOCKED_ERROR, e.getMessage());
                    } else if (e instanceof NotPermissionException) {
                        return R.restResult(ApiErrorCode.USER_NOPERMISSION_LOCKED_ERROR, e.getMessage());
                    } else if (e instanceof DisableServiceException) {
                        return R.restResult(ApiErrorCode.USER_DISABLE, ((DisableServiceException) e).getDisableTime() + "秒后解封");
                    } else {
                        return R.error(e.getMessage());
                    }
                })

                // 前置函数：在每次认证函数之前执行
                .setBeforeAuth(r -> {
//					SaRouter.match("/**", StpUtil::checkLogin);
                    // ---------- 设置一些安全响应头 ----------
                    SaHolder.getResponse()
                            // 服务器名称
                            .setServer("sa-server")
                            // 是否可以在iframe显示视图： DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
                            .setHeader("X-Frame-Options", "SAMEORIGIN")
                            // 是否启用浏览器默认XSS防护： 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时，停止渲染页面
                            .setHeader("X-Frame-Options", "1; mode=block")
                            // 禁用浏览器内容嗅探
                            .setHeader("X-Content-Type-Options", "nosniff")
                    ;
                });
    }

    @Bean
    public Scheduler jobInit() throws SchedulerException {
        return new StdSchedulerFactory().getScheduler();
    }

//    @Bean
//    public void jsonInit(@Inject SnackRenderFactory factory, @Inject SnackActionExecutor executor){
//        //示例1：通过转换器，做简单类型的定制
////        factory.addConvertor(Date.class, s -> s.getTime());
////
////        factory.addConvertor(LocalDate.class, s -> s.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
////
////        factory.addConvertor(Double.class, s -> String.valueOf(s));
////
////        factory.addConvertor(BigDecimal.class, s -> s.toPlainString());
////
////        //示例2：通过编码器，做复杂类型的原生定制（基于框架原生接口）
////        factory.addEncoder(Date.class, (data, node) -> node.val().setNumber(data.getTime()));
//
//        //示例3：调整特性（例，添加枚举序列化为名字的特性）
////        factory.addFeatures(Feature.EnumUsingName);  //增加特性
//        factory.addFeatures(Feature.UseGetter);  //增加特性 //只使用 getter 属性做序列化输出
//    }

}




